package cn.xeblog.plugin.game.box;

import cn.hutool.core.util.StrUtil;
import cn.xeblog.plugin.game.box.enums.MapAttributeEnum;
import cn.xeblog.plugin.game.box.util.CustomMap;
import cn.xeblog.plugin.game.box.util.ImagesUtils;
import cn.xeblog.plugin.game.box.util.MapsUtils;
import lombok.Getter;

import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.util.Stack;

/**
 * 推箱子界面
 */
public class PushBoxUI extends JPanel implements ActionListener {
    /**
     * 地图行
     */
    private int mapRow;
    /**
     * 地图列
     */
    private int mapColumn;
    /**
     * 宽度
     */
    private int width;
    /**
     * 高度
     */
    private int height;

    /**
     * 方格大小
     */
    private final static int UNIT_SIZE = 30;
    /**
     * 默认宽度偏移量
     */
    private final static int DEFAULT_OFFSET_WIDTH = 10;
    /**
     * 默认高度偏移量
     */
    private final static int DEFAULT_OFFSET_HEIGHT = 50;
    // ----------------------- 人物坐标 -----------------------
    /**
     * 人物所在行
     */
    private int row;
    /**
     * 人物所在列
     */
    private int column;
    /**
     * 是否允许回退
     */
    private boolean couldBack = true;
    /**
     * 当前关卡
     */
    @Getter
    private int level;
    /**
     * 当前地图
     */
    private int[][] map;
    /**
     * 操作记录 用于撤回操作
     */
    private final Stack<CustomMap> snapshot = new Stack<>();

    public int getTheWidth() {
        return this.width;
    }

    public int getTheHeight() {
        return this.height;
    }

    /**
     * 构造
     *
     * @param level 关卡
     */
    public PushBoxUI(int level) {
        this.level = level;
        // 初始化当前地图数据
        initCurrentData();
        // 初始化面板
        initJPanel();
    }

    /**
     * 初始化面板
     */
    private void initJPanel() {
        // 可见，
        this.setLayout(null);
        this.setVisible(true);
        // 添加键盘监听
        this.addKeyListener(getKeyListener());
        // 可聚焦
        this.setFocusable(true);

        // 界面添加鼠标监听
        PushBoxUI pushBoxUI = this;
        this.addMouseListener(new MouseAdapter() {
            @Override
            public void mouseClicked(MouseEvent e) {
                // 聚焦
                pushBoxUI.requestFocusInWindow();
            }
        });
    }

    /**
     * 初始化地图
     */
    public void initCurrentData() {
        // 清除缓存步骤
        this.snapshot.clear();
        // 设置对应级别的地图
        this.map = MapsUtils.getLevel(level);
        // 设置游戏区域大小及显示游戏的左上角位置
        mapRow = map.length;
        mapColumn = map[0].length;
        // 设置宽高
        this.width = mapColumn * UNIT_SIZE;
        this.height = mapRow * UNIT_SIZE;
        // 设置人物当前位置
        for (int i = 0; i < map.length; i++) {
            for (int j = 0; j < map[0].length; j++) {
                MapAttributeEnum attr = MapAttributeEnum.match(map[i][j]);
                if (MapAttributeEnum.MAN_DOWN == attr || MapAttributeEnum.MAN_UP == attr || MapAttributeEnum.MAN_LEFT == attr || MapAttributeEnum.MAN_RIGHT == attr) {
                    row = i;
                    column = j;
                    break;
                }
            }
        }
    }

    /**
     * 刷新界面
     *
     * @param g 图形类
     */
    private void refreshUI(Graphics g) {
        // 允许回退
        couldBack = true;
        // 初始化当前地图数据
        initCurrentData();
        // 清除地图的方格窗口,加上额外的偏移量只是因为宽度在初始化时增加了偏移量,这里清除的时候补回来,
        g.clearRect(0, 0, width + DEFAULT_OFFSET_WIDTH, height + DEFAULT_OFFSET_HEIGHT);
        // 刷新界面
        updateUI();
    }

    /**
     * 刷新父容器
     *
     * @param level 级别
     */
    private void refreshParentUI(int level) {
        // 父面板
        JPanel parent = (JPanel) this.getParent();
        // 设置父面板的宽、高
        int[][] map = MapsUtils.getLevel(level);
        // 这里加额外的偏移量是因为上一关或者下一关刷新界面时内容不够
        int width = map[0].length * UNIT_SIZE + 40;
        int height = map.length * UNIT_SIZE + 50;
        parent.setMinimumSize(new Dimension(width, height));
        // 刷新界面
        parent.updateUI();
    }

    @Override
    public void paintComponent(Graphics g) {
        // 重新画图
        super.paintComponent(g);
        // 绘图
        for (int i = 0; i < mapRow; i++) {
            for (int j = 0; j < mapColumn; j++) {
                if (map[i][j] != 0) {
                    // 地图相关图片，如墙
                    Image image = ImagesUtils.getImageMap().get(map[i][j]);
                    g.clearRect(DEFAULT_OFFSET_WIDTH + j * UNIT_SIZE, DEFAULT_OFFSET_HEIGHT + i * UNIT_SIZE, UNIT_SIZE, UNIT_SIZE);
                    g.drawImage(image, DEFAULT_OFFSET_WIDTH + j * UNIT_SIZE, DEFAULT_OFFSET_HEIGHT + i * UNIT_SIZE, UNIT_SIZE, UNIT_SIZE, this);
                }
            }
        }

        // 绘制提示
        String tips = StrUtil.format("当前第{}关，已走{}步。", level, snapshot.size());
        g.setFont(new Font(null, Font.PLAIN, UNIT_SIZE / 2));
        g.drawString(tips, 10, 30);
    }

    /**
     * 向上移动
     */
    private void moveUp() {
        // 墙或者箱子后一格是墙，无法移动
        if (isWall(row - 1, column) || (isBox(row - 1, column) && isWall(row - 2, column))) {
            return;
        }

        // 添加快照
        addSnapshot();

        // 当前位置前方一格是箱子且前方二格是墙
        if (isBox(row - 1, column) && isBlank(row - 2, column)) {
            // 更改站立位置的属性
            changeStandData(row, column);
            // 人物向上一格，如果是箱子终点，标记为人物向上终点，否则,标记为人物向上
            changMapData(row - 1, column, MapAttributeEnum.BOX_TARGET.getValue(), MapAttributeEnum.MAN_UP_TARGET.getValue(), MapAttributeEnum.MAN_UP.getValue());
            // 人物向上二格，如果是终点，标记为箱子终点，否则,标记为箱子
            changMapData(row - 2, column, MapAttributeEnum.TARGET.getValue(), MapAttributeEnum.BOX_TARGET.getValue(), MapAttributeEnum.BOX.getValue());
            // 向上移动
            row--;
        } else if (isBlank(row - 1, column)) {  // 当前位置前方一格是过道
            // 更改站立位置的属性
            changeStandData(row, column);
            // 人物向上一格，如果是过道，标记为人物向上，否则,标记为人物向上终点
            changMapData(row - 1, column, MapAttributeEnum.BLANK.getValue(), MapAttributeEnum.MAN_UP.getValue(), MapAttributeEnum.MAN_UP_TARGET.getValue());
            // 向上移动
            row--;
        }
    }

    /**
     * 向下移动
     */
    private void moveDown() {
        // 墙或者箱子后一格是墙，无法移动
        if (isWall(row + 1, column) || (isBox(row + 1, column) && isWall(row + 2, column))) {
            //changMapData(row, column, MAN_DOWN_TARGET, MAN_DOWN_TARGET, MAN_DOWN);
            return;
        }
        // 添加快照
        addSnapshot();
        // 当前位置下方一格是箱子且下方二格是墙
        if (isBox(row + 1, column) && isBlank(row + 2, column)) {
            // 更改站立位置的属性
            changeStandData(row, column);
            // 人物向下一格，如果是箱子终点，标记为人物向上终点，否则,标记为人物向下
            changMapData(row + 1, column, MapAttributeEnum.BOX_TARGET.getValue(), MapAttributeEnum.MAN_DOWN_TARGET.getValue(), MapAttributeEnum.MAN_DOWN.getValue());
            // 人物向下二格，如果是终点，标记为箱子终点，否则,标记为箱子
            changMapData(row + 2, column, MapAttributeEnum.TARGET.getValue(), MapAttributeEnum.BOX_TARGET.getValue(), MapAttributeEnum.BOX.getValue());
            // 向下移动
            row++;
        } else if (isBlank(row + 1, column)) { // 当前位置下方一格是过道
            // 更改站立位置的属性
            changeStandData(row, column);
            // 人物向下一格，如果是过道，标记为人物向下，否则,标记为人物向下终点
            changMapData(row + 1, column, MapAttributeEnum.BLANK.getValue(), MapAttributeEnum.MAN_DOWN.getValue(), MapAttributeEnum.MAN_DOWN_TARGET.getValue());
            // 向下移动
            row++;
        }
    }

    /**
     * 向左移动
     */
    private void moveLeft() {
        // 墙或者箱子后一格是墙，无法移动
        if (isWall(row, column - 1) || (isBox(row, column - 1) && isWall(row, column - 2))) {
            //changMapData(row, column, MAN_LEFT_TARGET, MAN_LEFT_TARGET, MAN_LEFT);
            return;
        }
        // 添加快照
        addSnapshot();
        // 当前位置左边一格是箱子且左边二格是墙
        if (isBox(row, column - 1) && isBlank(row, column - 2)) {
            // 更改站立位置的属性
            changeStandData(row, column);
            // 人物向左一格，如果是箱子终点，标记为人物向左终点，否则,标记为人物向左
            changMapData(row, column - 1, MapAttributeEnum.BOX_TARGET.getValue(), MapAttributeEnum.MAN_LEFT_TARGET.getValue(), MapAttributeEnum.MAN_LEFT.getValue());
            // 人物向左二格，如果是终点，标记为箱子终点，否则,标记为箱子
            changMapData(row, column - 2, MapAttributeEnum.TARGET.getValue(), MapAttributeEnum.BOX_TARGET.getValue(), MapAttributeEnum.BOX.getValue());
            // 向左移动
            column--;
        } else if (isBlank(row, column - 1)) {  // 当前位置左边一格是过道
            // 更改站立位置的属性
            changeStandData(row, column);
            // 人物向左一格，如果是过道，标记为人物向左，否则,标记为人物向左终点
            changMapData(row, column - 1, MapAttributeEnum.BLANK.getValue(), MapAttributeEnum.MAN_LEFT.getValue(), MapAttributeEnum.MAN_LEFT_TARGET.getValue());
            // 向左移动
            column--;
        }
    }

    /**
     * 向右移动
     */
    private void moveRight() {
        // 墙或者箱子后一格是墙，无法移动
        if (isWall(row, column + 1) || (isBox(row, column + 1) && isWall(row, column + 2))) {
            //changMapData(row, column, MAN_RIGHT_TARGET, MAN_RIGHT_TARGET, MAN_RIGHT);
            return;
        }
        // 添加快照
        addSnapshot();

        // 当前位置右边一格是箱子且右边二格是墙
        if (isBox(row, column + 1) && isBlank(row, column + 2)) {
            // 更改站立位置的属性
            changeStandData(row, column);
            // 人物向右一格，如果是箱子终点，标记为人物向右终点，否则,标记为人物向右
            changMapData(row, column + 1, MapAttributeEnum.BOX_TARGET.getValue(), MapAttributeEnum.MAN_RIGHT_TARGET.getValue(), MapAttributeEnum.MAN_RIGHT.getValue());
            // 人物向右二格，如果是终点，标记为箱子终点，否则,标记为箱子
            changMapData(row, column + 2, MapAttributeEnum.TARGET.getValue(), MapAttributeEnum.BOX_TARGET.getValue(), MapAttributeEnum.BOX.getValue());
            // 向右移动
            column++;
        } else if (isBlank(row, column + 1)) { // 当前位置右边一格是过道
            // 更改站立位置的属性
            changeStandData(row, column);
            // 人物右边一格，如果是过道，标记为人物向右，否则,标记为人物向右终点
            changMapData(row, column + 1, MapAttributeEnum.BLANK.getValue(), MapAttributeEnum.MAN_RIGHT.getValue(), MapAttributeEnum.MAN_RIGHT_TARGET.getValue());
            // 向右移动
            column++;
        }
    }

    /**
     * 撤消，过关后不可撤销
     */
    public void undo() {
        // 允许回退
        if (couldBack) {
            // 存在快照
            if (snapshot.size() > 0) {
                // 弹出
                CustomMap priorCustomMap = snapshot.pop();
                // 设置地图和人物所在位置
                map = priorCustomMap.getMap();
                row = priorCustomMap.getManX();
                column = priorCustomMap.getManY();
                // 刷新界面
                repaint();
            } else {
                JOptionPane.showMessageDialog(null, "都撤销到姥姥家了！无法再撤销！", "提示", JOptionPane.INFORMATION_MESSAGE);
            }
        } else { // 不允许回退
            JOptionPane.showMessageDialog(null, "本关已完成，无法撤销！", "提示", JOptionPane.INFORMATION_MESSAGE);
        }
    }

    /**
     * 上一关
     */
    public void prevLevel() {
        // 关卡不大于1，则没有上一关
        if (level > 1) {
            // 关卡减1
            level--;
            // 刷新界面
            refreshParentUI(level);
            refreshUI(this.getGraphics());
        }
    }

    /**
     * 下一关
     */
    public void nextLevel() {
        // 关卡大于等于总关卡，则没有下一关
        if (level >= MapsUtils.getTotal()) {
            couldBack = false;
            int choice = JOptionPane.showConfirmDialog(null, "恭喜通过全部通关！是否从第一关开始？", "干得漂亮！", JOptionPane.YES_NO_OPTION);
            if (choice == 0) {
                // 设置为第一关
                level = 1;
            } else {
                return;
            }
        } else {
            // 关卡加1
            level++;
        }

        // 刷新界面
        refreshParentUI(level);
        refreshUI(this.getGraphics());
    }

    /**
     * 存地图快照 用于撤回操作
     */
    private void addSnapshot() {
        snapshot.push(new CustomMap(row, column, MapsUtils.copy2dArrays(map)));
    }

    /**
     * 判断是不是箱子
     *
     * @param row    行
     * @param column 列
     * @return true, 是箱子，否则，false
     */
    private boolean isBox(int row, int column) {
        return map[row][column] == MapAttributeEnum.BOX.getValue() || map[row][column] == MapAttributeEnum.BOX_TARGET.getValue();
    }

    /**
     * 判断是否是过道或者终点
     *
     * @param row    行
     * @param column 列
     * @return true, 是过道或者终点，否则，false
     */
    private boolean isBlank(int row, int column) {
        return map[row][column] == MapAttributeEnum.BLANK.getValue() || map[row][column] == MapAttributeEnum.TARGET.getValue();
    }

    /**
     * 判断是不是墙
     *
     * @param row    行
     * @param column 列
     * @return true, 是墙，否则，false
     */
    private boolean isWall(int row, int column) {
        return map[row][column] == MapAttributeEnum.WALL.getValue();
    }

    /**
     * 更改站立位置的属性
     *
     * @param row    行
     * @param column 列
     */
    private void changeStandData(int row, int column) {
        // 站立的位置
        int stand = map[row][column];
        // 如果位置是终点，那么设置站立为终点，否则为过道
        stand = stand == MapAttributeEnum.MAN_LEFT_TARGET.getValue() || stand == MapAttributeEnum.MAN_RIGHT_TARGET.getValue() || stand == MapAttributeEnum.MAN_UP_TARGET.getValue() || stand == MapAttributeEnum.MAN_DOWN_TARGET.getValue() ? MapAttributeEnum.TARGET.getValue() : MapAttributeEnum.BLANK.getValue();
        map[row][column] = stand;
    }

    /**
     * 改变指定位置图案值
     *
     * @param row       行
     * @param column    列
     * @param compare   比较值
     * @param correct   true对应值
     * @param inCorrect false对应值
     */
    private void changMapData(int row, int column, int compare, int correct, int inCorrect) {
        map[row][column] = map[row][column] == compare ? correct : inCorrect;
    }

    @Override
    public void actionPerformed(ActionEvent e) {
        // 发生操作时调用
    }

    /**
     * 获取键盘监听事件
     *
     * @return 键盘监听事件
     */
    private KeyListener getKeyListener() {
        return new KeyListener() {
            @Override
            public void keyPressed(KeyEvent e) {
                int keyCode = e.getKeyCode();
                switch (keyCode) {
                    case KeyEvent.VK_UP:
                        moveUp();
                        break;
                    case KeyEvent.VK_DOWN:
                        moveDown();
                        break;
                    case KeyEvent.VK_LEFT:
                        moveLeft();
                        break;
                    case KeyEvent.VK_RIGHT:
                        moveRight();
                        break;
                    case KeyEvent.VK_PAGE_UP:
                        prevLevel();
                        return;
                    case KeyEvent.VK_PAGE_DOWN:
                        nextLevel();
                        return;
                    case 8: // 兼容MacOS
                    case KeyEvent.VK_DELETE:
                        // 撤消
                        undo();
                        return;
                    default:
                        break;
                }
                // 刷新界面
                repaint();
                // 检查游戏是否结束
                checkFinished();
            }

            @Override
            public void keyReleased(KeyEvent e) {
            }

            @Override
            public void keyTyped(KeyEvent e) {

            }
        };
    }

    /**
     * 判断游戏是否结束
     */
    private void checkFinished() {
        boolean finished = true;
        for (int i = 0; i < mapRow; i++) {
            for (int j = 0; j < mapColumn; j++) {
                // 只要存在终点或者人物方向向上、下、左、右为终点时，说明存在终点，结束应该是将终点变成箱子，即箱子把终点给盖住了，只要没盖住就不算结束
                if (map[i][j] == MapAttributeEnum.TARGET.getValue() || map[i][j] == MapAttributeEnum.MAN_DOWN_TARGET.getValue() || map[i][j] == MapAttributeEnum.MAN_UP_TARGET.getValue() || map[i][j] == MapAttributeEnum.MAN_LEFT_TARGET.getValue() || map[i][j] == MapAttributeEnum.MAN_RIGHT_TARGET.getValue()) {
                    finished = false;
                    break;
                }
            }
        }

        // 游戏已结束
        if (finished) {
            // 不通回退了
            couldBack = false;
            // 弹出确认框
            String msg = StrUtil.format("恭喜你通过第{}关！！！本关共移动{}步。\n是否要进入下一关？", level, snapshot.size());
            int choice = JOptionPane.showConfirmDialog(null, msg, "恭喜过关！", JOptionPane.YES_NO_OPTION);
            if (choice == 0) { // 确认ok,
                // 能回退
                couldBack = true;
                // 下一关
                nextLevel();
            }
        }
    }

}
